use crate::stream::{CommandStream, DataStream};
use std::collections::HashMap;
use std::net::{TcpStream, TcpListener};
use std::sync::{Mutex, Arc};
use std::thread;
use std::io::Read;

pub struct Server {
    listener: Arc<TcpListener>,
    cmd_stream: Arc<Mutex<CommandStream>>,
    proxy_map: HashMap<u16, ServerProxy>,
    listen_host: String
}

impl Server {
    pub fn new(host: &str, port: u16, listen_host: &str) -> Server {
        let tcp = TcpListener::bind((host, port))
            .expect(format!("Failed to listen {}:{}", host, port).as_str());
        println!("Listening: {}:{}", host, port);
        let (stream, info) = tcp.accept()
            .expect("Failed to build connection with client");
        let cmd_stream = CommandStream::new(stream);
        println!("Connection built {}:{}", info.ip(), info.port());
        Server {
            listener: Arc::new(tcp),
            cmd_stream: Arc::new(Mutex::new(cmd_stream)),
            proxy_map: HashMap::new(),
            listen_host: String::from(listen_host)
        }
    }

    pub fn run(&mut self) {
        loop {
            let copy = self.cmd_stream.clone();
            let mut cmd_stream = copy.lock().unwrap();
            let cmd = match cmd_stream.read() {
                Ok(s) => s,
                Err(_) => {
                    eprintln!("Control flow I/O error");
                    std::process::exit(-1);
                }
            };
            let end = self.exec_cmd(cmd);
            if end { break; }
        }
        let copy_stream = self.cmd_stream.clone();
        thread::spawn(move || {
            let copy_stream = copy_stream;
            let mut cmd_stream;
            {
                let t = copy_stream.lock().unwrap();
                cmd_stream = t.try_clone_stream().unwrap();
            }
            loop {
                let mut buf = [0u8];
                match cmd_stream.read(&mut buf) {
                    Ok(_) => {}
                    Err(_) => {
                        eprintln!("Client closed connection");
                        std::process::exit(0);
                    }
                }
            }
        });
        let listener = self.listener.clone();
        for stream in listener.incoming() {
            let stream = match stream {
                Ok(s) => s,
                Err(_) => { continue; }
            };
            let mut data_stream = DataStream::new(stream);
            let head = match data_stream.read_head() {
                Ok(s) => s,
                Err(_) => { continue; }
            };
            let mut args = head.split(' ');
            let arg1 = match args.next() {
                Some(a) => a,
                None => { continue; }
            };
            let arg2 = match args.next() {
                Some(a) => a,
                None => { continue; }
            };
            let port = match arg1.parse::<u16>() {
                Ok(v) => v,
                Err(_) => { continue; }
            };
            let index = match arg2.parse::<u32>() {
                Ok(v) => v,
                Err(_) => { continue; }
            };
            self.ready(port, index, data_stream);
        }
    }

    fn exec_cmd(&mut self, cmd: String) -> bool {
        let mut args = cmd.split(' ');
        let arg0 = match args.next() {
            Some(arg) => arg,
            None => { eprintln!("WARNING: Empty command received."); return false; }
        };
        match arg0 {
            "listen" => {
                let arg1 = match args.next() {
                    Some(arg) => arg,
                    None => { eprintln!("WARNING: 'listen' command need an argument."); return false; }
                };
                self.listen(arg1);
            }

            "end" => {
                return true;
            }

            _ => {
                eprintln!("WARNING: '{}' command not found.", arg0);
            }
        }
        return false;
    }

    fn listen(&mut self, port: &str) {
        let port = match port.parse::<u16>(){
            Ok(p) => p,
            Err(_) => { eprintln!("WARNING: 'listen' command need a port."); return; }
        };
        match self.proxy_map.get(&port) {
            Some(_) => { return; }
            None => {}
        };
        let mut proxy = ServerProxy::new(self.listen_host.clone(), port);
        proxy.run(self.cmd_stream.clone());
        self.proxy_map.insert(port, proxy);
    }

    fn ready(&mut self, port: u16, index: u32, data_stream: DataStream) {
        let proxy = match self.proxy_map.get_mut(&port) {
            Some(p) => p,
            None => { return; }
        };
        proxy.pipe(index, data_stream);
    }
}

pub struct ServerProxy {
    host: String,
    port: u16,
    count: Arc<Mutex<u32>>,
    server_stream: Arc<Mutex<HashMap<u32, TcpStream>>>
}

impl ServerProxy {
    fn new(host: String, port: u16) -> ServerProxy {
        ServerProxy {
            host,
            port,
            count: Arc::new(Mutex::new(0u32)),
            server_stream: Arc::new(Mutex::new(HashMap::new()))
        }
    }

    fn run(&mut self, cmd_stream: Arc<Mutex<CommandStream>>) {
        let index = self.count.clone();
        let port = self.port;
        let server_stream = self.server_stream.clone();
        let cmd_stream = cmd_stream.clone();
        let listen_host = self.host.clone();
        thread::spawn(move || {
            let listener = match TcpListener::bind((listen_host, port)) {
                Ok(l) => l,
                Err(_) => { return; }
            };
            for stream in listener.incoming() {
                let stream = match stream {
                    Ok(s) => s,
                    Err(_) => { continue; }
                };
                let mut server_stream = match server_stream.lock() {
                    Ok(l) => l,
                    Err(_) => { continue; }
                };
                let mut cmd_stream = match cmd_stream.lock() {
                    Ok(l) => l,
                    Err(_) => { continue; }
                };
                let copy = index.clone();
                let mut index = match copy.lock() {
                    Ok(l) => l,
                    Err(_) => { continue; }
                };
                server_stream.insert(*index, stream);
                match cmd_stream.write(format!("request {} {}", port, *index)) {
                    Ok(_) => {}
                    Err(_) => { continue; }
                };
                *index += 1;
            }
        });
    }

    fn pipe(&mut self, index: u32, data_stream: DataStream) {
        let mut lock = match self.server_stream.lock() {
            Ok(l) => l,
            Err(_) => { return; }
        };
        let server_stream = match lock.remove(&index) {
            Some(s) => s,
            None => { return; }
        };
        let mut client_stream = data_stream;
        client_stream.pipe(server_stream);
    }
}
